/*
 * NyuContext.hpp
 *
 *  Created on: 2010/05/21
 *      Author: mitsu
 */

#ifndef NYUCONTEXT_HPP_
#define NYUCONTEXT_HPP_

#include <iostream>
#include <vector>
#include <cstring>
#include "NyuValue.hpp"
#include "NyuTree.hpp"
#include "NyuSymtbl.hpp"

class NyuScope;

struct NyuSymref {
    NyuValue value;
    NyuScope* scope;
    NyuSymref* next;

    NyuSymref(NyuValue v, NyuScope* s, NyuSymref* n) :
        value(v), scope(s), next(n) {}
};

class NyuScope {
public:
	NyuScope(NyuScope* parent = 0, NyuValue block = 0) :
		parent_(parent), block_(block), argc_(0) {
	}
	~NyuScope() {
	}

    NyuScope* parent() {
        return parent_;
    }

    NyuValue block() {
    	return block_;
    }

    void init(NyuScope* parent, NyuValue block) {
    	parent_ = parent;
    	block_ = block;
    	argc_ = 0;
    }

private:
	NyuScope* parent_;
	NyuValue block_;
    NyuValue argv_[NYU_MAXARGS];
    int argc_;

    std::vector<unsigned int> scope_symbols_;

    friend class NyuContext;
    friend class NyuGC;
};

class NyuContext {
public:
	NyuContext();
	virtual ~NyuContext();

	NyuScope* current_scope() {
		return current_scope_;
	}

	NyuValue current_block() {
		return current_scope_->block_;
	}

	NyuValue* current_argv() {
		return current_scope_->argv_;
	}

    NyuValue current_argv(int n) {
        if (n < 0 || current_scope_->argc_ <= n)
            return 0;
        return current_scope_->argv_[n];
    }

	int current_argc() {
		return current_scope_->argc_;
	}

	void set_current_argc(int argc) {
	    current_scope_->argc_ = argc;
	}

    void push_scope(NyuValue block, NyuTreeList* args) {
        NyuScope* scope;

        if (scope_cache_idx_) {
            scope = scope_cache_[--scope_cache_idx_];
            scope->init(current_scope_, block);
        } else {
            scope = new_scope(current_scope_, block);
        }

        scope->argv_[0] = current_target_;

        int i = 1;
        for (NyuTreeList* l = args; l; l = l->next(), ++i) {
            scope->argv_[i] = l->value()->eval(this);
        }

        scope->argc_ = i;

        current_scope_ = scope;
    }

    void update_scope(NyuValue block, NyuTreeList* args) {
        NyuValue tmp_argv[NYU_MAXARGS];

        tmp_argv[0] = current_target_;

        int i = 1;
        for (NyuTreeList* l = args; l; l = l->next(), ++i) {
            tmp_argv[i] = l->value()->eval(this);
        }

        ::memcpy(current_scope_->argv_, tmp_argv, sizeof(NyuValue) * i);

        current_scope_->argc_ = i;
        current_scope_->block_ = block;

        is_scope_updated_ = true;
    }

    void pop_scope() {
        NyuScope* scope = current_scope_;

        current_scope_ = scope->parent_;

        typedef std::vector<unsigned int>::iterator iter;
        for (iter it = scope->scope_symbols_.begin(),
                end = scope->scope_symbols_.end(); it != end; ++it) {
            NyuSymref*& ref = symbols_[*it];
            NyuSymref* tmp = ref;
            ref = ref->next;
            delete tmp;
        }

        if (scope_cache_idx_ < NYU_SCOPE_CACHE_SIZE) {
            scope->scope_symbols_.clear();
            scope_cache_[scope_cache_idx_++] = scope;
        } else {
            delete_scope(scope);
        }
    }

    bool is_scope_updated() {
    	return is_scope_updated_;
    }

    void set_is_scope_updated(bool is_scope_updated) {
    	is_scope_updated_ = is_scope_updated;
    }

    NyuSymref* get_symref(unsigned int idx) {
        if (symbols_.size() <= idx)
            update_symbols();
        return symbols_[idx];
    }

    NyuSymref* put_symref(unsigned int idx, NyuValue v2) {
        NyuSymref*& ref = symbols_[idx];

        // TODO これもキャッシュしたほうがいいかも
        ref = new NyuSymref(v2, current_scope_, ref);

        current_scope_->scope_symbols_.push_back(idx);

        return ref;
    }

    // symbolsをリサイズ
    void update_symbols() {
        symbols_.resize(NyuSymtbl::getSize());
    }

    void set_func(const char* name, NyuFunc func);

    NyuValue getCurrentTarget() {
        return current_target_;
    }

    void setCurrentTarget(NyuValue target) {
        current_target_ = target;
    }

    NyuValue callValue(NyuValue v1, bool is_tail = false);
    NyuValue callValue(NyuValue v1, NyuValue v2, bool is_tail = false);

private:
    static NyuScope* new_scope(NyuScope* parent, NyuValue block);
    static void delete_scope(NyuScope* scope);

private:
	NyuScope* current_scope_;
    NyuScope* scope_cache_[NYU_SCOPE_CACHE_SIZE];
    int scope_cache_idx_;
    bool is_scope_updated_;

    std::vector<NyuSymref*> symbols_;

    NyuValue current_target_;

    friend class NyuGC;
};

#endif /* NYUCONTEXT_HPP_ */
